package net.gdface.sdk;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

import net.gdface.image.ImageErrorException;
import net.gdface.image.BaseLazyImage;
import net.gdface.image.NotImageException;
import net.gdface.image.UnsupportedFormatException;
import net.gdface.sdk.fse.FeatureSe;
import net.gdface.utils.Assert;
import net.gdface.utils.ShareLock;

/**
 * {@link FaceApi}本地实现<br>
 * SDK接口类本地实现必须从此类派生<br>
 * @author guyadong
 *
 */
public abstract class BaseFaceApiLocal extends BaseFaceApi {
	protected final NotFaceDetectedException NOTFACEDETECTED_INSTANCE_DEFAULT = new NotFaceDetectedException();
	/**
	 * 并发锁,限制建模的并发线程数,默认为CPU核数
	 */
	protected static ShareLock concurrentLock = new ShareLock(Runtime.getRuntime().availableProcessors());
	protected static IAuxTool auxTool = new IAuxTool() {		
		@Override
		public void saveOnFail(String phase, BaseLazyImage lazyImage) {
			// DO NOTHING
		}
	};
	public static synchronized void setAuxTool(IAuxTool auxTool) {
		if(null != auxTool){
			BaseFaceApiLocal.auxTool = auxTool;
		}
	}
	protected BaseFaceApiLocal() {}
	/**
	 * 通过图像数据字节流(byte[])创建FaceImage对象<br>
	 * 调用{@link BaseLazyImage#setAutoClose(boolean)}设置 autClose为{@code false}<br>
	 * 如果有特殊要求，子类可以重写此方法
	 * @param imgData
	 * @return
	 * @throws UnsupportedFormatException
	 * @throws NotImageException
	 * @see BaseLazyImage#open()
	 * @see BaseLazyImage#setAutoClose(boolean)
	 */
	protected BaseLazyImage makeOpenedLazyImage(byte[] imgData) throws UnsupportedFormatException, NotImageException{
		return BaseLazyImage.getLazyImageFactory().create(imgData).setAutoClose(false).open();
	}
	@Override
	public CodeInfo[] detectAndGetCodeInfo(byte[] imgData, int faceNum) throws ImageErrorException, NotFaceDetectedException {
		BaseLazyImage lazyImg = makeOpenedLazyImage(imgData);
		try {
			return detectAndGetCodeInfo(lazyImg, faceNum);
		} finally {
			try {
				lazyImg.close();
				lazyImg = null;
			} catch (IOException e) {
				throw new RuntimeException(e);
			}
		}
	}

	/**
	 * 对图像({@code lazyImg})中指定的人脸位置{@code facePos}并提取特征码，返回生成人脸特征码数据数组对象（人脸可能不止一个）<br>
	 * 调用该方法时假设图像({@code lazyImage})能正常解码,所以当对图像解码出现异常时，将异常对象封装到{@link RuntimeException}抛出<br>
	 * 
	 * @param lazyImg
	 *            图像对象,为{@code null}则抛出 {@link IllegalArgumentException}
	 * @param faceNum
	 * @param facePos
	 * @return 返回{@link CodeInfo}对象列表<br>
	 *         对应于{@code facePos}中的每个{@link FRect}对象,返回一个{@link CodeInfo}对象(数组下标相同):<br>
	 *         如果{@link FRect}对象为{@code null},则返回一个所有成员都为{@code null}的{@link CodeInfo}对象<br>
	 *         如果在{@link FRect}指定的范围没有提取到特征码,则{@link CodeInfo#getCode()},{@link CodeInfo#getEi()}返回{@code null}
	 * @throws NotFaceDetectedException 
	 * @see #getCodeFromImageMatrix(byte[], int, int, CodeInfo, AtomicBoolean)
	 * @see FaceApi#getCodeInfo(byte[], int, CodeInfo[])
	 */
	public List<CodeInfo> getCodeInfo(BaseLazyImage lazyImg, int faceNum, List<CodeInfo> facePos) throws NotFaceDetectedException {
		concurrentLock.lock();
		// 检测到的人脸数目
		int faceCount = 0;
		// 提取到特征码的人脸数目
		int featureCount=0;
		try {
			Assert.notNull(lazyImg, "lazyImg");
			// facePos为null或空时抛出异常
			Assert.notEmpty(facePos, "facePos");
			// 人脸位置对象数目小于要求提取的特征码的数目,则抛出异常
			if (facePos.size() < faceNum){
				throw new NotFaceDetectedException(facePos.size(), 0);
			}
			byte[] imgData=getMatrixData(lazyImg);
			byte[][] features = nativeGetFaceFeatures(imgData, lazyImg.getWidth(), lazyImg.getHeight(),facePos);
			for(int i = 0 ;i<features.length;++i){
				if(null!=facePos.get(i)){
					++faceCount;
					if(null!=features[i]){
						++featureCount;
					}
				}
			}
			// 提取到的人脸特征数目不等于要求提取的特征码的数目,则抛出异常
			if ((faceNum > 0)) {
				if (featureCount != faceNum){
					throw new NotFaceDetectedException(faceCount, featureCount);
				}
			} else if (0 == featureCount){
				throw new NotFaceDetectedException(faceCount, 0);
			}
			for(int i=0;i<facePos.size();++i){
				CodeInfo ci = facePos.get(i);
				if(null!=ci){
					ci.setCode(features[i]);
				}
			}
			if(featureCount != faceCount){
				auxTool.saveOnFail("getCodeInfo", lazyImg);
			}
			return facePos;
		} catch (UnsupportedFormatException e) {
			throw new RuntimeException(e);
		} finally {
			concurrentLock.unlock();
		}	
	}
	/**
	 * 提取单个人脸特征 
	 * @param lazyImg
	 * @param facePos
	 * @return
	 */
	public CodeInfo getCodeInfo(BaseLazyImage lazyImg, CodeInfo facePos){
		concurrentLock.lock();
		try {
			byte[] imgData=getMatrixData(lazyImg);
			byte[] feature = nativeGetFaceFeature(imgData, lazyImg.getWidth(), lazyImg.getHeight(), facePos);
			facePos.setCode(feature);
			return feature == null ? null : facePos;
		} catch (UnsupportedFormatException e) {
			return null;
		} finally {
			concurrentLock.unlock();
		}
	}
	@Override
	public CodeInfo getCodeInfo(byte[] imgData, CodeInfo facePos){
		 try{
			BaseLazyImage lazyImg = makeOpenedLazyImage(imgData);
			 return getCodeInfo(lazyImg, facePos);
		 }catch (ImageErrorException e) {
			 throw new RuntimeException(e);
		}		
	}
	/**
	 * 先对图像数据{@code imgData}进行人脸检测，然后提取人脸特征码<br>
	 * 
	 * @param lazyImg
	 * @param faceNum
	 * @return
	 * @throws ImageErrorException
	 * @throws NotFaceDetectedException 
	 * @see #detectAndGetCodeInfo(byte[], int)
	 */
	public CodeInfo[] detectAndGetCodeInfo(BaseLazyImage lazyImg, int faceNum) throws ImageErrorException,
	NotFaceDetectedException {
		concurrentLock.lock();
		try{
			byte[] imgData = getMatrixData(lazyImg);
			CodeInfo[] codes = nativeDetectAndGetFeatures(imgData, lazyImg.getWidth(), lazyImg.getHeight());
			// 没有检测到要求的人脸则抛出异常
			if(0==codes.length){
				throw NOTFACEDETECTED_INSTANCE_DEFAULT;
			}
			if(faceNum>0&&faceNum!=codes.length){
				throw NOTFACEDETECTED_INSTANCE_DEFAULT;
			}
			return codes;
		}finally{
			concurrentLock.unlock();
		}		
}	

	@Override
	public CodeInfo[] detectFace(byte[] imgData) throws ImageErrorException {
		BaseLazyImage lazyImg = makeOpenedLazyImage(imgData);
		try {
			return detectFace(lazyImg).toArray(new CodeInfo[0]);
		} finally {
			try {
				lazyImg.close();
				lazyImg = null;
			} catch (IOException e) {
				throw new RuntimeException(e);
			}
		}
	}

	@Override
	public CodeInfo[] getCodeInfo(byte[] imgData, int faceNum, CodeInfo[] facePos) throws NotFaceDetectedException {
		BaseLazyImage lazyImg = null;
		try {
			lazyImg = makeOpenedLazyImage(imgData);
			return getCodeInfo(lazyImg, faceNum, null == facePos ? null : Arrays.asList(facePos)).toArray(
					new CodeInfo[0]);
		} catch (ImageErrorException e) {
			throw new RuntimeException(e);
		} finally {
			try {
				if (null != lazyImg) {
					lazyImg.close();
					lazyImg = null;
				}
			} catch (IOException e) {
				throw new RuntimeException(e);
			}
		}
	}

	@Override
	public final double compareCode(byte[] code1, byte[] code2) {
		Assert.assertValidCode(code1, code2);
		return nativeCompareCode(code1,code2);
	}
	@Override
	public final boolean isLocal() {
		return true;
	}
	/**
	 * 对图像({@code lazyImg})进行人脸检测，返回人脸位置数据对象{@link CodeInfo}列表<br>
	 * 
	 * @param lazyImg
	 *            图像对象,为{@code null}时抛出{@link IllegalArgumentException}
	 * @return 没有检测到人脸则返回空表
	 * @throws UnsupportedFormatException
	 */
	public List<CodeInfo> detectFace(BaseLazyImage lazyImg)
			throws UnsupportedFormatException {
		concurrentLock.lock();
		try {
			Assert.notNull(lazyImg, "bufImg");
			List<CodeInfo> faceInfo = new ArrayList<CodeInfo>();
			if (lazyImg.getWidth() > 0 && lazyImg.getHeight() > 0) {
				byte[] imgRGB =getMatrixData(lazyImg);
				nativeDetectFace(imgRGB,lazyImg.getWidth(), lazyImg.getHeight(),faceInfo);
			}
			return faceInfo;
		} finally {
			concurrentLock.unlock();
		}
	}
	/**
	 * 从LazyImage对象中获取指定获取的图像矩阵数据(用于检测和建模)<br>
	 * 默认返回BGR格式图像数据,算法具体实现可以根据自己的需要图像格式重写此方法.
	 * @param lazyImg
	 * @return
	 * @throws UnsupportedFormatException
	 */
	protected byte[] getMatrixData(BaseLazyImage lazyImg) throws UnsupportedFormatException {
		return lazyImg.getMatrixBGR();
	}
	/**
	 * 参见 {@link #compareCode(byte[], byte[])}
	 * @param code1
	 * @param code2
	 * @return
	 */
	protected abstract double nativeCompareCode(byte[] code1, byte[] code2) ;
	
	/**
	 * 检测人脸
	 * @param imgMatrix 图像矩阵数据,数据格式要求与算法相关
	 * @param width 图像宽度
	 * @param height 图像高度
	 * @param faceInfo 保存检测到的人脸位置信息
	 */
	public abstract void nativeDetectFace(byte[] imgMatrix, int width, int height, List<CodeInfo> faceInfo);
	/**
	 * 对faceinfo指定的人脸位置提取特征
	 * @param imgMatrix 图像矩阵数据,数据格式要求与算法相关
	 * @param width 图像宽度
	 * @param height 图像高度
	 * @param faceInfo
	 * @return 人脸特征数据,提取特征失败则返回null
	 */
	public abstract byte[] nativeGetFaceFeature(byte[] imgMatrix, int width, int height, CodeInfo faceInfo);
	/**
	 * 根据检测到的人脸位置{@code facePos},提取一组特征
	 * @param imgMatrix 图像矩阵数据,数据格式要求与算法相关
	 * @param width 图像宽度
	 * @param height 图像高度
	 * @param facePos
	 * @return 返回facePos列表中每个人脸位置对应的人脸特征数据，提取特征失败的对应为null
	 */
	public byte[][] nativeGetFaceFeatures(byte[] imgMatrix, int width, int height, List<CodeInfo> facePos) {
		byte[][] res = new byte[facePos.size()][];
		for(int i=0;i<facePos.size();++i){
			CodeInfo face = facePos.get(i);
			if(null != face){
				res[i]=nativeGetFaceFeature(imgMatrix, width, height, face);
				face.setCode(res[i]);
			}else{
				res[i] = null;
			}
		}		
		return res;			
	}
	/**
	 * 检测并提取特征
	 * @param imgMatrix 图像矩阵数据,数据格式要求与算法相关
	 * @param width 图像宽度
	 * @param height 图像高度
	 * @return
	 */
	public CodeInfo[] nativeDetectAndGetFeatures(byte[] imgMatrix, int width, int height) {
		LinkedList<CodeInfo> faceInfo = new LinkedList<CodeInfo>();
		nativeDetectFace(imgMatrix, width, height, faceInfo);
		for( CodeInfo face:faceInfo){
			face.setCode(nativeGetFaceFeature(imgMatrix, width, height, face));
		}
		return faceInfo.toArray(new CodeInfo[faceInfo.size()]);
	}
	/**
	 * 返回当前{@link FaceApi}接口实例对应的搜索引擎接口实例，如果没有则返回{@code null}
	 * @return
	 */
	public FeatureSe getFeatureSe(){
		return null;
	} 
}